#pragma once

#include "Socket.hpp"
#include "Epoller.hpp"
#include <sys/epoll.h>
#include <iostream>
#include <memory>

using namespace NetWork;
using namespace EpollerModule;

const static int g_max_events = 1024;

class EpollServer
{
public:
    EpollServer(int port): _port(port), 
                           _is_running(false), 
                           _listen_socket(new TcpSocket()),
                           _revs(new struct epoll_event[g_max_events]),
                           _epoller(new Epoller())
    {}
    ~EpollServer()
    {
        delete[] _revs;
    }

    void init()
    {
        // 1. create listen socket
        _listen_socket->BuildListenSocketMethod(_port);

        // 2. initialize epoller
        _epoller->init();

        // 3. add listen socket to epoller
        _epoller->addEvent(_listen_socket->GetSockfd(), EPOLLIN);
    }

    void loop()
    {
        _is_running = true;
        while (_is_running)
        {
            //int timeout = -1;  // for test
            int timeout = 3000;
            int num_events = _epoller->wait(_revs, g_max_events, timeout);

            switch(num_events)
            {
            case -1:
                std::cerr << "epoll wait error, error : " << strerror(errno) << std::endl;
                break;
            case 0:
                std::cout << "epoll wait timeout..." << std::endl;
                break;
            default:  // num_events > 0
                HandleEvents(num_events);
                break;
            }
        }
        _is_running = false;
    }

private:
    void HandleEvents(int num_events)
    {
        std::cout << num_events << " events is ready..." << std::endl;
        for (int i = 0; i < num_events; i++)
        {
            printf("ready fd : %d, event is %s\n", _revs[i].data.fd, _event2string(_revs[i].events).c_str());

            int sockfd = _revs[i].data.fd;
            uint32_t events = _revs[i].events;

            if(events & EPOLLIN)
            {
                // 1) accept new connection
                if(sockfd == _listen_socket->GetSockfd())
                {
                    std::string client_ip;
                    uint16_t client_port;
                    int new_sockfd = _accepter(client_ip, client_port);
                    if(new_sockfd > 0)
                    {
                        std::cout << "new connection from " << client_ip << ":" << client_port 
                                  << ", fd is " << new_sockfd << std::endl;
                    }
                }
                // 2) handle new data
                else
                {
                    std::string data;
                    int nbytes = _recver(sockfd, &data);
                    if(nbytes > 0)
                    {
                        std::cout << "recv data from " << sockfd << ", data is " << data << std::endl;
                        // TODO: handle data
                        data.resize(data.size() - strlen("\r\n"));
                        std::string echo_msg = "echo: " + data + "\r\n";
                        int nsent = send(sockfd, echo_msg.c_str(), echo_msg.size(), 0);
                        if(nsent < 0)
                        {
                            std::cerr << "send error to " << sockfd << ", error : " << strerror(errno) << std::endl;
                        }
                    }
                    else
                    {
                        if(nbytes == 0)
                        {
                            std::cout << "connection closed by " << sockfd << std::endl;
                        }
                        else
                        {
                            std::cerr << "recv error from " << sockfd << ", error : " << strerror(errno) << std::endl;
                        }
                        
                        _epoller->delEvent(sockfd);
                        close(sockfd);
                    }
                }
            }
        }
    }

private:
    std::string _event2string(uint32_t event)
    {
        switch(event)
        {
        case EPOLLIN:
            return "EPOLLIN";
        case EPOLLOUT:
            return "EPOLLOUT";
        case EPOLLPRI:
            return "EPOLLPRI";
        case EPOLLERR:
            return "EPOLLERR";
        case EPOLLHUP:
            return "EPOLLHUP";
        case EPOLLET:
            return "EPOLLET";
        case EPOLLONESHOT:
            return "EPOLLONESHOT";
        case EPOLLRDNORM:
            return "EPOLLRDNORM";
        case EPOLLRDBAND:
            return "EPOLLRDBAND";
        case EPOLLWRNORM:
            return "EPOLLWRNORM";
        case EPOLLWRBAND:
            return "EPOLLWRBAND";
        case EPOLLMSG:
            return "EPOLLMSG";
        case EPOLLRDHUP:
            return "EPOLLRDHUP";
        default:
            return "unknown event";
        }
    }

    int _accepter(std::string& client_ip, uint16_t& client_port)
    {
        // 1. accept new connection
        int new_sockfd = _listen_socket->AcceptSocketOrDie(&client_ip, &client_port);
        // 2. add new connection to epoller
        _epoller->addEvent(new_sockfd, EPOLLIN);
        return new_sockfd;
    }

    int _recver(int sockfd, std::string* data)
    {
        char buffer[1024];
        int nbytes = recv(sockfd, buffer, sizeof(buffer), 0);
        if(nbytes > 0)
        {
            buffer[nbytes] = '\0';
            *data = buffer;
        }
        return nbytes;
    }
    
private:
    std::unique_ptr<Socket> _listen_socket;
    std::unique_ptr<Epoller> _epoller;
    int _port;
    bool _is_running;
    struct epoll_event* _revs;
};